package edu.scu.declaresys.dao.impl;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.SimpleExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.scu.declaresys.config.Configs;
import edu.scu.declaresys.dao.HibernateSessionFactory;
import edu.scu.declaresys.dao.intr.IGeneralDAO;
import edu.scu.declaresys.dao.util.UpdateHelper;
import edu.scu.declaresys.exception.ExecuteException;
import edu.scu.declaresys.util.PageList;
import edu.scu.declaresys.util.PagerUtil;

/**
 * <pre>
 * 通用DAO。
 * 1.查找深度：depth,
 * depth = 0，表示不查找外部引用。
 * depth = 1，表示查找到下一层的外部引用。
 * depth = 2，表示查找到下下一层的外部引用。
 * depth = 3, ...
 * 2.传入参数为null，表示不使用该参数所表示的功能
 * </pre>
 * @author: zt
 * @since : 2013-7-3
 */
public class GeneralDAOImpl<T> implements IGeneralDAO<T> {
	
	protected static final Logger	log	= LoggerFactory.getLogger(GeneralDAOImpl.class);
	
	/**
	 * Class of type T
	 */
	protected Class<T> clzz;
	
	//
	// Constructor
	//
	public GeneralDAOImpl(Class<T> clzz)
	{
		this.clzz = clzz;
	}
	
	//
	// Utilities.
	//
	@Override
	public Session getSession() {
		return HibernateSessionFactory.getSession();
	}
	
	@Override
	public void closeSession()
	{
		HibernateSessionFactory.closeSession();
		return ;
	}
	
	//
	// Read methods.
	//
	/**
	 * 根据ID查找并返回。
	 * @return vo if success, or null if error happened.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T findById(Integer id, int depth) throws ExecuteException
	{
		log.debug("getting " + clzz.getSimpleName() + " instance with id: " + id);
		T vo = null;
		Session session = getSession();
		try
		{
			vo = (T) session.get(clzz, id);
			if(depth > 0)
			{
				fillReference(vo, depth);
			}
		}
		catch(RuntimeException re)
		{
			log.error("get failed", re);
			throw new ExecuteException(Configs.isDebug ? "findById" + re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		return vo;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T findByPropertyUnique(String propertyName, Object value, int depth)
			throws ExecuteException
	{
		log.debug("finding " + clzz.getSimpleName() + "instance by property");
		try
		{
			Session session = getSession();
			Criteria criteria = session.createCriteria(clzz);
			if(propertyName != null)
			{
				criteria.add(Restrictions.eq(propertyName, value));
			}
			criteria.setMaxResults(1);
			return (T)criteria.uniqueResult();
		}
		catch(RuntimeException re)
		{
			log.error("find by property failed", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public T findByExampleUnique(T vo, int depth) throws ExecuteException
	{
		log.debug("finding " + clzz.getSimpleName() + "instance by example");
		try
		{
			Criteria criteria = getSession().createCriteria(clzz)
							.add(Example.create(vo));
			criteria.setMaxResults(1);
			return (T)criteria.uniqueResult();
		}
		catch(RuntimeException re)
		{
			log.error("find by example failed", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
	}
	
	/**
	 * 查询时vo中的列，用与条件进行连接
	 * 注意：按ID查找不能使用该函数，就使用findByProperty.
	 * 当vo中不设置任何值时，等价于查找所有元素
	 */
	@Override
	public PageList<T> findByExample(T vo, int depth, Integer pageNum,
			Integer amtPerPage, String orderColumn, Boolean isAsc)
			throws ExecuteException
	{
		log.debug("finding " + clzz.getSimpleName() + "instance by example");
		PageList<T> voLst = new PageList<T>();
		try
		{
			Criteria criteria = getSession().createCriteria(clzz)
							.add(Example.create(vo));
			dealPageAndOrder(criteria, depth, pageNum, amtPerPage, orderColumn, isAsc, voLst);
		}
		catch(RuntimeException re)
		{
			log.error("find by example failed", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		return voLst;
	}
	
	/**
	 * 当propertyName不指定时，等价与查找所有元素。
	 */
	@Override
	public PageList<T> findByProperty(String propertyName, Object value, int depth,
			Integer pageNum, Integer amtPerPage, String orderColumn,
			Boolean isAsc) throws ExecuteException
	{
		log.debug("finding " + clzz.getSimpleName() + "instance by property");
		PageList<T> voLst = new PageList<T>();
		try
		{
			Session session = getSession();
			Criteria criteria = session.createCriteria(clzz);
			if(value != null)
			{
				criteria.add(Restrictions.eq(propertyName, value));
			}
			dealPageAndOrder(criteria, depth, pageNum, amtPerPage, orderColumn, isAsc, voLst);
		}
		catch(RuntimeException re)
		{
			log.error("find by property failed", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		return voLst;
	}
	
	/**
	 * 当propertyName不指定时，等价与查找所有元素。
	 */
	@Override
	public PageList<T> findByProperty(SimpleExpression exp, int depth,
			Integer pageNum, Integer amtPerPage, String orderColumn,
			Boolean isAsc) throws ExecuteException
	{
		log.debug("finding " + clzz.getSimpleName() + "instance by property");
		PageList<T> voLst = new PageList<T>();
		try
		{
			Session session = getSession();
			Criteria criteria = session.createCriteria(clzz);
			if(exp != null)
			{
				criteria.add(exp);
			}
			dealPageAndOrder(criteria, depth, pageNum, amtPerPage, orderColumn, isAsc, voLst);
		}
		catch(RuntimeException re)
		{
			log.error("find by property failed", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		return voLst;
	}

	@Override
	public PageList<T> findByStringLike(String propertyName, String value,
			int depth, Integer pageNum, Integer amtPerPage, String orderColumn,
			Boolean isAsc) throws ExecuteException
	{
		log.debug("finding " + clzz.getSimpleName() + "instance by String like.");
		PageList<T> voLst = new PageList<T>();
		try
		{
			Session session = getSession();
			Criteria criteria = session.createCriteria(clzz);
			if(value != null)
			{
				criteria.add(Restrictions.like(propertyName, value, MatchMode.ANYWHERE));
			}			
			dealPageAndOrder(criteria, depth, pageNum, amtPerPage, orderColumn, isAsc, voLst);
		}
		catch(RuntimeException re)
		{
			log.error("find by property failed", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		return voLst;
	}
	
	//
	// Write methods.
	//
	@Override
	public Integer save(T vo) throws ExecuteException
	{
		log.debug("saving " + clzz.getSimpleName() + " instance");
		Integer id = null;
		Session session = getSession();
		Transaction trans = null;
		try
		{
			trans = session.beginTransaction();
			id = (Integer)session.save(vo);
			trans.commit();			
		}
		catch(RuntimeException re)
		{
			if(trans != null)
			{
				trans.rollback();
			}
			log.debug("save failed!", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		return id;
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void update(T vo, Integer id) throws ExecuteException
	{
		log.debug("updating " + clzz.getSimpleName() + " instance");
		Session session = getSession();
		Transaction trans = null;
		try
		{
			trans = session.beginTransaction();
			T oldVo = (T)session.get(clzz, id);
			oldVo = new UpdateHelper<T>(clzz).update(oldVo, vo);
			session.update(oldVo);
			trans.commit();			
		}
		catch(RuntimeException re)
		{
			if(trans != null)
			{
				trans.rollback();
			}
			log.debug("update failed!", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
		
	}

	@SuppressWarnings("unchecked")
	@Override
	public void delete(int id) throws ExecuteException
	{
		log.debug("deleting " + clzz.getSimpleName() + " instance");
		Session session = getSession();
		Transaction trans = null;
		try
		{
			trans = session.beginTransaction();
			T vo = (T)session.get(clzz, id);
			session.delete(vo);
			trans.commit();			
		}
		catch(RuntimeException re)
		{
			if(trans != null)
			{
				trans.rollback();
			}
			log.debug("delete failed!", re);
			throw new ExecuteException(Configs.isDebug ? re.getMessage() : "");
		}
		finally
		{
			closeSession();
		}
	}

	
	//
	// help functions.
	//
	protected void fillReference(T vo, int depth) throws ExecuteException
	{
	}
	
	/**
	 * 处理分页、排序，并返回查找结果
	 *@param criteria
	 *@param depth
	 *@param pageNum
	 *@param amtPerPage
	 *@param orderColumn
	 *@param isAsc
	 *@return
	 * @throws ExecuteException 
	 */
	@SuppressWarnings("unchecked")
	protected void dealPageAndOrder(Criteria criteria, int depth, Integer pageNum, Integer amtPerPage, String orderColumn, Boolean isAsc, PageList<T> voLst) throws ExecuteException
	{
		// 检查是否需要查数据总量
		if(pageNum != null)
		{
			ProjectionList pLst = Projections.projectionList();
			pLst.add(Projections.rowCount());
			criteria.setProjection(pLst);
			//获取数据总量
			voLst.setDataAmt((Integer)criteria.list().get(0));
			criteria.setProjection(null);
		}
		//检查是否需要排序
		if(orderColumn != null && isAsc != null)
		{
			criteria.addOrder(isAsc ? Order.asc(orderColumn) : Order.desc(orderColumn));
		}	
		//检查是否需要设置偏移量
		if(pageNum != null && amtPerPage != null)
		{
			criteria.setFirstResult(PagerUtil.getFirstResult(pageNum, amtPerPage));
			voLst.setCrtPageNum(pageNum);
		}
		//检查是否需要设置最大查找数量
		if(amtPerPage != null)
		{
			criteria.setMaxResults(amtPerPage);
			voLst.setAmtPerPage(amtPerPage);
		}
		//查询并添加结果
		voLst.addAll(criteria.list());
		if(depth > 0)
		{
			for(T item : voLst)
			{
				fillReference(item, depth);
			}
		}
		return ;
	}
}